package me.zhengjie.common.utils

import me.zhengjie.logging.slf4j.*
import cn.hutool.core.collection.CollUtil
import cn.hutool.core.util.ObjectUtil
import me.zhengjie.common.annotation.Query
import jakarta.persistence.criteria.*

import java.lang.reflect.Field
import java.util.*


/**
 * @author Kuki Wu
 * @date 2019-6-4 14:59:48
 */

object QueryHelp {
    fun <T> getPredicate(root: Root<T>, query: Any?, cb: CriteriaBuilder): Predicate {
        val list: MutableList<Predicate> = ArrayList()
        if (query == null) {
            return cb.and(*list.toTypedArray())
        }
        try {
            val fields = getAllFields(query.javaClass, ArrayList())
            for (field: Field in fields) {
                //val accessible = field.canAccess(field)
                val accessible = field.isAccessible
                field.setAccessible(true)
                //field.isAccessible = true
                val q = field.getAnnotation(Query::class.java)
                if (q != null) {
                    val propName = q.propName
                    val joinName = q.joinName
                    val blurry = q.blurry
                    val attributeName = if (isBlank(propName)) field.name else propName
                    val fieldType = field.type
                    val `val` = field.get(query)

                    if (ObjectUtil.isNull(`val`) || "" == `val`) {
                        continue
                    }
                    var join: Join<*, *>? = null
                    // 模糊多字段
                    if (ObjectUtil.isNotEmpty(blurry)) {
                        val blurrys = blurry.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
                        val orPredicate: MutableList<Predicate> = ArrayList()
                        for (s in blurrys) {
                            orPredicate.add(
                                cb.like(
                                    root.get<Any>(s)
                                        .`as`(String::class.java), "%$`val`%"
                                )
                            )
                        }
                        val p = arrayOfNulls<Predicate>(orPredicate.size)
                        list.add(cb.or(*p))
                        continue
                    }
                    if (ObjectUtil.isNotEmpty(joinName)) {
                        val joinNames = joinName.split(">")
                        for (name in joinNames) {
                            when (q.join) {
                                Query.Join.LEFT -> if (ObjectUtil.isNotNull(join)) {
                                    join = join!!.join<Any,Any>(name, JoinType.LEFT)
                                } else {
                                    join = root.join<Any, Any>(name, JoinType.LEFT)
                                }

                                Query.Join.RIGHT -> if (ObjectUtil.isNotNull(join)) {
                                    join = join!!.join<Any,Any>(name, JoinType.RIGHT)
                                } else {
                                    join = root.join<Any, Any>(name, JoinType.RIGHT)
                                }

                                else -> {}
                            }
                        }
                    }

                    when (q.type) {
                        Query.Type.EQUAL -> list.add(
                            cb.equal(
                                getExpression<Any, T>(attributeName, join, root)
                                    .`as`(fieldType as Class<out Comparable<*>?>), `val` as Comparable<Any?>
                            )
                        )

                        Query.Type.GREATER_THAN -> list.add(
                            cb.greaterThanOrEqualTo(
                                getExpression(attributeName, join, root), `val` as Comparable<Any?>
                            )
                        )

                        Query.Type.LESS_THAN -> list.add(
                            cb.lessThanOrEqualTo (
                                getExpression(attributeName, join, root), `val` as Comparable<Any?>
                            )
                        )

                        Query.Type.LESS_THAN_NQ -> list.add(
                            cb.lessThan(
                                getExpression(attributeName, join, root), `val` as Comparable<Any?>
                            )
                        )

                        Query.Type.INNER_LIKE -> list.add(
                            cb.like(
                                getExpression<Any, T>(attributeName, join, root)
                                    .`as`(String::class.java), "%$`val`%"
                            )
                        )

                        Query.Type.LEFT_LIKE -> list.add(
                            cb.like(
                                getExpression<Any, T>(attributeName, join, root)
                                    .`as`(String::class.java), "%$`val`"
                            )
                        )

                        Query.Type.RIGHT_LIKE -> {
                            list.add(
                                cb.like(
                                    getExpression<Any,T>(attributeName, join, root)
                                        .`as`(String::class.java), "$`val`%"
                                )
                            )
                            if (CollUtil.isNotEmpty(`val` as Collection<Long?>)) {
                                list.add(getExpression<Any, T>(attributeName, join, root).`in`(`val`))
                            }
                        }

                        Query.Type.IN -> if (CollUtil.isNotEmpty(`val` as Collection<Long?>)) {
                            list.add(getExpression<Any, T>(attributeName, join, root).`in`(`val`))
                        }

                        else -> {}
                    }
                }
                //field.isAccessible = accessible
                field.setAccessible(accessible)
            }
        } catch (e: Exception) {
            logDebug(e.message.toString())
        }
        val size = list.size
        return cb.and(*list.toTypedArray())
    }


    fun <T,R> getExpression(attributeName: String, join: Join<*, *>?, root: Root<R>): Expression<T> {
        return if (ObjectUtil.isNotEmpty(join)) {
            join!!.get<T>(attributeName)
        } else {
            root.get<T>(attributeName)
        }
    }

    @JvmStatic
    fun isBlank(cs: CharSequence?): Boolean {
        var strLen: Int =0
        if (cs == null || cs.length.also { strLen = it } == 0) {
            return true
        }
        for (i in 0 until strLen) {
            if (!Character.isWhitespace(cs[i])) {
                return false
            }
        }
        return true
    }

    fun getAllFields(clazz: Class<*>?, fields: MutableList<Field>): List<Field> {
        if (clazz != null) {
            fields.addAll(Arrays.asList(*clazz.declaredFields))
            getAllFields(clazz.superclass, fields)
        }
        return fields
    }
}

